1 /*
2 Copyright (c)
2014 Andrew Jones, Dario Seyb
3  Based
on 'Spriter2Unity' python code by Malhavok
4
5 Permission
is hereby granted, free of charge, to any person obtaining a copy
6 of
this software and associated documentation files (the "Software"), to deal
7 in
the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software
is
10 furnished to
do so, subject to the following conditions:
11
12 The above copyright notice and
this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED
"AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 THE SOFTWARE.
22 */

23 using
System;
24 using
System.Collections.Generic;
25 using
System.Linq;
26 using
System.Text;
27 using
UnityEngine;
28 using
UnityEditor;
29 using
System.IO;
30 using
Assets.ThirdParty.Spriter2Unity.Editor.Spriter;
31
32 namespace
Assets.ThirdParty.Spriter2Unity.Editor.Unity
33 {
34     
using Animation = Spriter.SpriterAnimation;
35     
public class AnimationBuilder
36     {
37         
private struct SpriteChangeKey
38         {
39             
public Sprite Sprite;
40             
public float Time;
41         }
42
43         Dictionary<Timeline, GameObject> lastGameObjectCache =
new Dictionary<Timeline, GameObject>(); //Used to determine active/inactive toggle
44         Dictionary<Timeline, TimelineKey> lastKeyframeCache =
new Dictionary<Timeline, TimelineKey>();
45
46         List<AnimationEvent> animationEvents =
new List<AnimationEvent>();
47
48         
private string spriteBaseFolder;
49         
50         ///
<summary>
51         ///
Holds a list of sprite change key frames for each sprite object in the hierarchy. Indexed by the relative path to the object.
52         ///
</summary>
53         Dictionary<
string, List<SpriteChangeKey>> spriteChangeKeys = new Dictionary<string, List<SpriteChangeKey>>();
54
55         AnimationCurveBuilder acb;
56
57         
public List<AnimationClip> BuildAnimationClips(GameObject root, Entity entity, string scmlAssetPath)
58         {
59             
var allAnimClips = AssetDatabase.LoadAllAssetRepresentationsAtPath(scmlAssetPath).OfType<AnimationClip>().ToList();
60             Debug.Log(
string.Format("Found {0} animation clips at {1}", allAnimClips.Count, scmlAssetPath));
61
62             
var newAnimClips = new List<AnimationClip>();
63
64             
foreach (var animation in entity.Animations)
65             {
66                 
var animClip = MakeAnimationClip(root, animation, Path.GetDirectoryName(scmlAssetPath));
67                 Debug.Log(
string.Format("Added animClip({0}) to asset path ({1}) WrapMode:{2}", animClip.name, scmlAssetPath, animClip.wrapMode));
68                 newAnimClips.Add(animClip);
69
70                 
var originalAnimClip = allAnimClips.Where(clip => clip.name == animClip.name).FirstOrDefault();
71                 
if (originalAnimClip != null)
72                 {
73                     Debug.Log(
"Replacing animation clip " + animClip.name);
74                     EditorUtility.CopySerialized(animClip, originalAnimClip);
75                     allAnimClips.Remove(originalAnimClip);
76                 }
77                 
else
78                     AssetDatabase.AddObjectToAsset(animClip, scmlAssetPath);
79             }
80
81             
//Remove any animation clips that are no longer present in the SCML
82             
foreach(var clip in allAnimClips)
83             {
84                 
//This may be a bad idea
85                 UnityEngine.Object.DestroyImmediate(clip,
true);
86             }
87
88             
return newAnimClips;
89         }
90
91         
public AnimationClip MakeAnimationClip(GameObject root, Animation animation, string baseFolderPath)
92         {
93             
//Clear local caches
94             lastGameObjectCache.Clear();
95             animationEvents.Clear();
96             lastKeyframeCache.Clear();
97             spriteChangeKeys.Clear();
98             spriteBaseFolder = baseFolderPath;
99
100             
var animClip = new AnimationClip();
101             animClip.name = animation.Name;
102
103             
//Set clip to Generic type
104             
//AnimationUtility.SetAnimationType(animClip, ModelImporterAnimationType.Generic);
105
106             
//Populate the animation curves & events
107             MakeAnimationCurves(root, animClip, animation);
108
109             
return animClip;
110         }
111
112         
private void MakeAnimationCurves(GameObject root, AnimationClip animClip, Animation animation)
113         {
114             acb =
new AnimationCurveBuilder();
115
116             
//Get a list of all sprites on this GO
117             
var allSprites = root.GetComponentsInChildren<Transform>(true).Select(sr => AnimationUtility.CalculateTransformPath(sr.transform, root.transform));
118             
119             
//Add a key for all objects on the first frame
120             SetGameObjectForKey(root, animClip, animation.MainlineKeys.First(),
0);
121
122             
foreach (var mainlineKey in animation.MainlineKeys)
123             {
124                 
125                 
var visibleSprites = SetGameObjectForKey(root, animClip, mainlineKey);
126                 
var hiddenSprites = allSprites.Except(visibleSprites);
127                 HideSprites(root, hiddenSprites, mainlineKey.Time);
128             }
129
130             
switch (animation.LoopType)
131             {
132                 
case LoopType.True:
133                     
//Cycle back to first frame
134                     SetGameObjectForKey(root, animClip, animation.MainlineKeys.First(), animation.Length);
135                     
break;
136                 
case LoopType.False:
137                     
//Duplicate the last key at the end time of the animation
138                     SetGameObjectForKey(root, animClip, animation.MainlineKeys.Last(), animation.Length);
139                     
break;
140                 
default:
141                     Debug.LogWarning(
"Unsupported loop type: " + animation.LoopType.ToString());
142                     
break;
143             }
144
145             
//Add the curves to our animation clip
146             
//NOTE: This MUST be done before modifying the settings, thus the double switch statement
147             acb.AddCurves(animClip);
148
149             
foreach (var sprite in spriteChangeKeys)
150             {
151                 
if (sprite.Value.Count > 0)
152                 {
153                     BuildSpriteChangeCurve(
ref animClip, sprite);
154                 }
155             }
156
157             
//Set the loop/wrap settings for the animation clip
158             
var animSettings = AnimationUtility.GetAnimationClipSettings(animClip);
159             
switch(animation.LoopType)
160             {
161                 
case LoopType.True:
162                     animClip.wrapMode = WrapMode.Loop;
163                     animSettings.loopTime =
true;
164                     
break;
165                 
case LoopType.False:
166                     animClip.wrapMode = WrapMode.ClampForever;
167                     
break;
168                 
case LoopType.PingPong:
169                     animClip.wrapMode = WrapMode.PingPong;
170                     animSettings.loopTime =
true;
171                     
break;
172                 
default:
173                     Debug.LogWarning(
"Unsupported loop type: " + animation.LoopType.ToString());
174                     
break;
175             }
176
177             animClip.SetAnimationSettings(animSettings);
178
179             
//Debug.Log(string.Format("Setting animation {0} to {1} loop mode (WrapMode:{2} LoopTime:{3}) ", animClip.name, animation.LoopType, animClip.wrapMode, animSettings.loopTime));
180         }
181
182         
private void HideSprites(GameObject root, IEnumerable<string> relativePaths, float time)
183         {
184             
foreach(var relativePath in relativePaths)
185             {
186                 
//Find the gameObject based on relative path
187                 
var transform = root.transform.Find(relativePath);
188                 
if (transform == null)
189                 {
190                     Debug.LogError(
"ERROR: Unable to find GameObject at relative path " + relativePath);
191                     
return;
192                 }
193
194                 
var gameObject = transform.gameObject;
195                 gameObject.SetActive(
false);
196
197                 acb.SetCurveActiveOnly(root.transform, transform, time);
198             }
199         }
200
201         
private HashSet<string> SetGameObjectForKey(GameObject root, AnimationClip animClip, MainlineKey mainlineKey, float time = -1)
202         {
203             HashSet<
string> paths = new HashSet<string>();
204             
//Could do this recursively - this is easier
205             Stack<Ref> toProcess =
new Stack<Ref>(mainlineKey.GetChildren(null));
206
207             
while (toProcess.Count > 0)
208             {
209                 
var next = toProcess.Pop();
210
211                 paths.Add(next.RelativePath);
212                 SetGameObjectForRef(root, next, time);
213                 SetSpriteEvent(animClip, time, next);
214
215                 
var children = mainlineKey.GetChildren(next);
216                 
foreach (var child in children) toProcess.Push(child);
217             }
218
219             
return paths;
220         }
221
222         
private void SetGameObjectForRef(GameObject root, Ref childRef, float time)
223         {
224             TimelineKey key = childRef.Referenced;
225             
if (time < 0) time = key.Time;
226
227             TimelineKey lastKey;
228             lastKeyframeCache.TryGetValue(key.Timeline,
out lastKey);
229
230             
//Get the relative path based on the current hierarchy
231             
var relativePath = childRef.RelativePath;
232
233             
//If this is the root, skip it
234             
if (string.IsNullOrEmpty(relativePath))
235             {
236                 Debug.Log(
"Skipping root node in SetGameObjectForRef (SHOULD NEVER HAPPEN)");
237                 
return;
238             }
239
240
241             
//Find the gameObject based on relative path
242             
var transform = root.transform.Find(relativePath);
243             
if (transform == null)
244             {
245                 Debug.LogError(
"ERROR: Unable to find GameObject at relative path " + relativePath);
246                 
return;
247             }
248
249             
var gameObject = transform.gameObject;
250             gameObject.SetActive(
true);
251
252             
//Get transform data from ref
253             Vector3 localPosition;
254             Vector3 localScale;
255             Vector3 localEulerAngles;
256
257             childRef.BakeTransforms(
out localPosition, out localEulerAngles, out localScale);
258
259             
//Set the current GameObject's transform data
260             transform.localPosition = localPosition;
261             transform.localScale = localScale;
262
263             
//Spin the object in the correct direction
264             
var oldEulerAngles = transform.localEulerAngles;
265             
266             
if (oldEulerAngles.z - localEulerAngles.z > 180) localEulerAngles.z += 360;
267             
else if (localEulerAngles.z - oldEulerAngles.z > 180) localEulerAngles.z -= 360;
268             
/*
269             
switch(childRef.Unmapped.Spin)
270             {
271                 
case SpinDirection.Clockwise:
272                     
while (oldEulerAngles.z > localEulerAngles.z) localEulerAngles.z += 360;
273                     
break;
274                 
case SpinDirection.CounterClockwise:
275                     
while (oldEulerAngles.z < localEulerAngles.z) localEulerAngles.z -= 360;
276                     
break;
277             }*/

278             transform.localEulerAngles = localEulerAngles;
279
280             
int zIndex = -1;
281             
var spriteKey = key as SpriteTimelineKey;
282             
if (spriteKey != null)
283             {
284                 zIndex = ((ObjectRef)childRef).ZIndex;
285                 
//transform.GetComponent<SpriteRenderer>().sortingOrder = zIndex;
286             }
287
288             acb.SetCurve(root.transform, transform, time, lastKey, zIndex);
289            
290
291             
//Get last-used game object for this Timeline - needed to clean up reparenting
292             GameObject lastGameObject;
293             
if (lastGameObjectCache.TryGetValue(key.Timeline, out lastGameObject) && gameObject != lastGameObject)
294             {
295                 
//Let Unity handle the global->local position cruft for us
296                 lastGameObject.transform.position = transform.position;
297                 lastGameObject.transform.eulerAngles = transform.eulerAngles;
298
299                 
//TODO: Also need to do something about scale - this is a little more tricky
300                 lastGameObject.transform.localScale = localScale;
301
302                 
//Deactivate the old object
303                 lastGameObject.SetActive(
false);
304
305                 acb.SetCurve(root.transform, lastGameObject.transform, time, lastKey);
306             }
307
308             
//Set cached value for last keyframe
309             lastKeyframeCache[key.Timeline] = key;
310         }
311
312         
private void BuildSpriteChangeCurve(ref AnimationClip clip, KeyValuePair<string, List<SpriteChangeKey>> timeline)
313         {
314             
// First you need to create Editor Curve Binding
315             EditorCurveBinding curveBinding =
new EditorCurveBinding();
316
317             
// I want to change the sprites of the sprite renderer, so I put the typeof(SpriteRenderer) as the binding type.
318             curveBinding.type =
typeof(SpriteRenderer);
319
320             
// Regular path to the GameObject that will be changed
321             curveBinding.path = timeline.Key;
322
323             
// This is the property name to change the sprite of a sprite renderer
324             curveBinding.propertyName =
"m_Sprite";
325
326             
// An array to hold the object keyframes
327             ObjectReferenceKeyframe[] keyFrames =
new ObjectReferenceKeyframe[timeline.Value.Count];
328
329             
int i = 0;
330             
foreach (var key in timeline.Value)
331             {
332                 keyFrames[i] =
new ObjectReferenceKeyframe();
333                 
// set the time
334                 keyFrames[i].time = key.Time;
335                 
// set reference for the sprite you want
336                 keyFrames[i].
value = key.Sprite;
337                 i++;
338
339             }
340
341             AnimationUtility.SetObjectReferenceCurve(clip, curveBinding, keyFrames);
342         }

343
344
345         ///
<summary>
346         ///
Recursively calls SetActive on transform and all children
347         ///
</summary>
348         
private void SetActiveRecursive(Transform root, bool isActive)
349         {
350             
foreach (Transform child in root.transform)
351             {
352                 SetActiveRecursive(child, isActive);
353             }
354             root.gameObject.SetActive(isActive);
355         }

356
357         ///
<summary>
358         ///
Creates an event to change the sprite for the specified Ref (if applicable)
359         ///
</summary>
360         ///
<param name="clip">Target AnimationClip for Event</param>
361         ///
<param name="time">Time at which event should be triggered</param>
362         ///
<param name="reference"></param>
363         
private void SetSpriteEvent(AnimationClip clip, float time, Ref reference)
364         {
365             
var spriteKey = reference.Referenced as SpriteTimelineKey;
366             
//Only add event for SpriteTimelineKey objects
367             
if (spriteKey != null)
368             {
369                 
if (time < 0) time = spriteKey.Time;
370                 
if (!spriteChangeKeys.ContainsKey(reference.RelativePath))
371                 {
372                     spriteChangeKeys[reference.RelativePath] =
new List<SpriteChangeKey>();
373                 }
374
375                 
//Add the key to the dictionary to later build all the curves at once.
376                 spriteChangeKeys[reference.RelativePath].Add(
new SpriteChangeKey() { Time = time, Sprite = AssetUtils.GetSpriteAtPath(spriteKey.File.Name, spriteBaseFolder) });
377             }
378         }
379     }
380 }


Dictionary lastGameObjectCache = new Dictionary(); Used to determine activeinactive toggle

Holds a list of sprite change key frames for each sprite object in the hierarchy. Indexed by the relative path to the object.

Remove any animation clips that are no longer present in the SCML

This may be a bad idea

Clear local caches

Set clip to Generic type

AnimationUtility.SetAnimationType(animClip, ModelImporterAnimationType.Generic);

Populate the animation curves & events

Get a list of all sprites on this GO

Add a key for all objects on the first frame

Cycle back to first frame

Duplicate the last key at the end time of the animation

Add the curves to our animation clip

NOTE: This MUST be done before modifying the settings, thus the double switch statement

Set the loopwrap settings for the animation clip

Debug.Log(string.Format("Setting animation {0} to {1} loop mode (WrapMode:{2} LoopTime:{3}) ", animClip.name, animation.LoopType, animClip.wrapMode, animSettings.loopTime));

Find the gameObject based on relative path

Could do this recursively - this is easier

Get the relative path based on the current hierarchy

If this is the root, skip it

Find the gameObject based on relative path

Get transform data from ref

Set the current GameObject's transform data

Spin the object in the correct direction

transform.GetComponent().sortingOrder = zIndex;

Get last-used game object for this Timeline - needed to clean up reparenting

Let Unity handle the global->local position cruft for us

TODO: Also need to do something about scale - this is a little more tricky

Deactivate the old object

Set cached value for last keyframe

First you need to create Editor Curve Binding

I want to change the sprites of the sprite renderer, so I put the typeof(SpriteRenderer) as the binding type.

Regular path to the GameObject that will be changed

This is the property name to change the sprite of a sprite renderer

An array to hold the object keyframes

set the time

set reference for the sprite you want

Recursively calls SetActive on transform and all children

Creates an event to change the sprite for the specified Ref (if applicable)

Target AnimationClip for Event

Time at which event should be triggered

Only add event for SpriteTimelineKey objects

Add the key to the dictionary to later build all the curves at once.




Trò chơi đua xe động vật trong UNITY Engine 114.909 lượt xem

Gõ tìm kiếm nhanh...